package org.smile.db.criteria;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.smile.collection.CollectionUtils;
import org.smile.collection.MapUtils;
import org.smile.db.PageModel;
import org.smile.db.handler.KeyColumnSwapper;
import org.smile.db.handler.OneFieldRowHandler;
import org.smile.db.handler.ResultSetMap;
import org.smile.db.jdbc.NoLambdaRecordDao;
import org.smile.db.sql.ArrayBoundSql;
import org.smile.db.sql.BoundSql;
import org.smile.util.ObjectLenUtils;

public abstract class AbstractNoLambdaCriteria<E> implements NoLambdaCriteria<E> {
	/**key 转换器*/
	protected KeyColumnSwapper keyColumnSwapper;
	/**
	 * 指定查询条件
	 */
	protected List<Criterion> criterions=new LinkedList<Criterion>();
	/**
	 * 指定排序字段
	 */
	protected List<Criterion> orderby=new LinkedList<Criterion>();
	/**
	 * 指定的查询字段
	 */
	protected List<String> queryFields=new ArrayList<String>();
	/**
	 * 更新设置的参数
	 */
	protected List<SetCriterion> setters = new ArrayList<>();
	/**
	 * 查询分页偏移行
	 */
	protected long offset=0;
	/**
	 * 查询条数限制
	 */
	protected int limit=0;

	/**
	 * 生成条件语句的查询与参数
	 * @return
	 */
	protected BoundSql buildQueryBoundSql() {
		StringBuilder whereSql=new StringBuilder();
		List<Object> params=new LinkedList<Object>();
		RecordCriterionVisitor visitor=new RecordCriterionVisitor(whereSql,params);
		for(Criterion c:this.criterions) {
			c.accept(visitor);
		}
		if(this.orderby.size()>0) {
			whereSql.append(" ORDER BY ");
			for(Criterion c:this.orderby) {
				c.accept(visitor);
			}
		}
		return new ArrayBoundSql(whereSql.toString(), params.toArray());
	}

	protected  class RecordCriterionVisitor extends WhereCriterionVisitor{

		/***参数占位符*/
		String parameterFlag="?";
		/**查询条件参数值*/
		protected List<Object> params;

		RecordCriterionVisitor(StringBuilder whereSql,List<Object> params){
			this.whereSql=whereSql;
			this.params=params;
		}

		@Override
		protected String toColumnName(String fieldName) {
			if(keyColumnSwapper!=null){
				return keyColumnSwapper.keyToColumn(fieldName);
			}
			return fieldName;
		}

		@Override
		public void visit(SimpleCriterion criterion) {
			whereSql.append(criterion.getFieldName()).append(" ").append(criterion.getOp()).append(" ");
			Object paramValue=criterion.getValue();
			if(Restrictions.IN.equalsIgnoreCase(criterion.getOp())
					||Restrictions.NOT_IN.equalsIgnoreCase(criterion.getOp())) {
				if(ObjectLenUtils.hasLength(paramValue)){
					int len=ObjectLenUtils.len(paramValue);
					StringBuilder sb=new StringBuilder("(");
					for(int j=0;j<len;j++){
						sb.append(" ? ");
						if(j<len-1){
							sb.append(",");
						}
						Object indexVal=ObjectLenUtils.get(paramValue,j);
						this.params.add(indexVal);
					}
					sb.append(")");
					whereSql.append(sb);
				}else {
					whereSql.append("(?)");
					this.params.add(paramValue);
				}
			}else {
				whereSql.append("?");
				this.params.add(paramValue);
			}
			whereSql.append(" ");
		}

		@Override
		public void visit(BetweenCriterion criterion) {
			whereSql.append(" ").append(criterion.getFieldName()).append(" between ").append(parameterFlag).append(" and ").append(parameterFlag).append(" ");
			params.add(criterion.getValue());
			params.add(criterion.getValue2());
		}
	}

	/**用于操作record的Dao实例*/
	protected NoLambdaRecordDao<E> noLambdaRecordDao;

	@Override
	public NoLambdaCriteria between(String fieldName, Object start, Object end) {
		checkAddAnd();
		criterions.add(Restrictions.between(fieldName, start, end));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> eq(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.eq(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> ne(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.ne(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> lt(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.lt(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> gt(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.gt(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> le(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.le(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> ge(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.ge(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> in(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.in(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> nin(String fieldName, Object value) {
		checkAddAnd();
		criterions.add(Restrictions.nin(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> like(String fieldName, String value, MatchMode mathMode) {
		checkAddAnd();
		criterions.add(Restrictions.like(fieldName, value,mathMode));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> like(String fieldName, String value) {
		checkAddAnd();
		criterions.add(Restrictions.like(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> eqother(String fieldName, String otherField) {
		checkAddAnd();
		criterions.add(Restrictions.eqother(fieldName, otherField));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> neother(String fieldName,String otherField) {
		checkAddAnd();
		criterions.add(Restrictions.neother(fieldName, otherField));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> ltother(String fieldName, String otherField) {
		checkAddAnd();
		criterions.add(Restrictions.ltother(fieldName, otherField));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> gtother(String fieldName, String otherField) {
		checkAddAnd();
		criterions.add(Restrictions.gtother(fieldName, otherField));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> leother(String fieldName, String otherField) {
		checkAddAnd();
		criterions.add(Restrictions.leother(fieldName, otherField));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> geother(String fieldName, String otherField) {
		checkAddAnd();
		criterions.add(Restrictions.geother(fieldName, otherField));
		return  this;
	}
	
	@Override
	public NoLambdaCriteria<E> and(Criterion criterion) {
		checkAddAnd();
		criterions.add(criterion);
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> or(Criterion criterion) {
		criterions.add(new OperatorCriterion(Restrictions.OR));
		criterions.add(criterion);
		return  this;
	}
	/**
	 * 	判断添加AND表达式
	 * @return
	 */
	protected NoLambdaCriteria<E> checkAddAnd() {
		if(criterions.size()>0) {
			criterions.add(new OperatorCriterion(Restrictions.AND));
		}
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> orderby(String fieldName) {
		orderby.add(new OrderbyCriterion(fieldName));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> fields(String first, String... fieldName) {
		queryFields.add(first);
		CollectionUtils.add(queryFields, fieldName);
		return  this;
	}
	@Override
	public NoLambdaCriteria<E> orderby(String fieldName, boolean desc) {
		orderby.add(new OrderbyCriterion(fieldName,desc?"desc":""));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> isnull(String fieldName) {
		checkAddAnd();
		criterions.add(Restrictions.isnull(fieldName));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> notnull(String fieldName) {
		checkAddAnd();
		criterions.add(Restrictions.notnull(fieldName));
		return  this;
	}



	@Override
	public NoLambdaCriteria<E> reset() {
		this.criterions.clear();
		this.orderby.clear();
		this.setters.clear();
		this.offset=0;
		this.limit=0;
		return  this;
	}

	@Override
	public String toString() {
		StringBuilder str=new StringBuilder(64);
		str.append("fields:").append(queryFields).append(",where:").append(this.criterions);
		str.append(",orderby:").append(orderby);
		return super.toString();
	}
	@Override
	public List<E> list(){
		BoundSql boundSql= buildQueryBoundSql();
		if(this.limit>0) {
			return noLambdaRecordDao.queryLimit(offset, limit,boundSql.getSql(), (Object[])boundSql.getParams());
		}
		return noLambdaRecordDao.query(boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public E first(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryFirst(boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public PageModel<E> queryPage(int page, int size){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryPage(page, size,boundSql.getSql(), (Object[])boundSql.getParams());
	}


	@Override
	public List<E> queryAll(){
		return noLambdaRecordDao.queryAll();
	}

	@Override
	public int deleteAll(){
		return noLambdaRecordDao.deleteAll();
	}

	@Override
	public E unique(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryUnique(boundSql.getSql(), (Object[])boundSql.getParams());
	}


	@Override
	public int delete(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.delete(boundSql.getSql(), (Object[])boundSql.getParams());
	}
	@Override
	public long count(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryCount(boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public List<E> top(int top){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryTop(top,boundSql.getSql(), (Object[])boundSql.getParams());
	}
	/**
	 * 转换单个查询字段
	 * @return
	 */
	private String oneField() {
		return this.queryFields.get(0);
	}
	/**
	 * 转换单个字段查询为数组形式
	 * @return
	 */
	private String[] oneFields() {
		return new String[]{this.queryFields.get(0)};
	}
	/**
	 * 转换查询字段
	 * @return
	 */
	private String[] allFields() {
		return this.queryFields.toArray(new String[this.queryFields.size()]);
	}

	@Override
	public <T> List<T> listField(Class<T> resClass){
		BoundSql boundSql= buildQueryBoundSql();
		if(this.limit>0) {
			return this.noLambdaRecordDao.queryLimitList(offset, limit, oneFields(), OneFieldRowHandler.instance(resClass),boundSql.getSql(), (Object[])boundSql.getParams());
		}
		return noLambdaRecordDao.queryOneFieldList(oneField(), resClass,boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public <T> T forField(Class<T> resClass){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryOneField(oneField(), resClass,boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public Integer forInt(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryForInt(oneField(),boundSql.getSql(), (Object[])boundSql.getParams());

	}

	@Override
	public Long forLong(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryForLong(oneField(),boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public String forString(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryForString(oneField(),boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public Double forDouble(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryForDouble(oneField(),boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public List<String> listString() {
		BoundSql boundSql= buildQueryBoundSql();
		if(this.limit>0) {
			return noLambdaRecordDao.queryLimitList(offset, limit, oneFields(), OneFieldRowHandler.STRING,boundSql.getSql(), (Object[])boundSql.getParams());
		}
		return noLambdaRecordDao.queryForStringList(oneField(),boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public <T> List<T> listObject(Class<T> res){
		BoundSql boundSql= buildQueryBoundSql();
		if(this.limit>0) {
			return noLambdaRecordDao.queryLimitObjectList(offset, limit, allFields(), res, boundSql.getSql(), (Object[])boundSql.getParams());
		}
		return noLambdaRecordDao.queryForObjectList(allFields(),res,boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public <T> T forObject(Class<T> res){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryForObject(allFields(),res,boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public List<ResultSetMap> listMap() {
		BoundSql boundSql= buildQueryBoundSql();
		if(this.limit>0) {
			return noLambdaRecordDao.queryLimitMapList(offset, limit, allFields(),boundSql.getSql(), (Object[])boundSql.getParams());
		}
		return noLambdaRecordDao.queryForMapList(allFields(),boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public ResultSetMap forMap(){
		BoundSql boundSql= buildQueryBoundSql();
		return noLambdaRecordDao.queryForMap(allFields(),boundSql.getSql(), (Object[])boundSql.getParams());
	}

	@Override
	public int update() {
		UpdateCriteriaInfo info = this.buildUpdateInfo();
		return noLambdaRecordDao.update(info.updateField,info.namedWhereSql,info.params);
	}

	/**
	 * 生成条件语句的查询与参数
	 * @return
	 */
	protected UpdateCriteriaInfo buildUpdateInfo() {
		StringBuilder whereSql=new StringBuilder();
		Map<String,Object> params= MapUtils.resultMap();
		CriterionVisitor visitor=getBuildUpdateInfoVisitor(whereSql,params);
		for(Criterion c:this.criterions) {
			c.accept(visitor);
		}
		String[] setFieldNames= new String[setters.size()];
		int idx=0;
		for(SetCriterion c:setters){
			setFieldNames[idx++] =c.getFieldName();
			params.put(c.getFieldName(),c.getValue());
		}
		return new UpdateCriteriaInfo(setFieldNames,whereSql.toString(), params);
	}

	protected abstract CriterionVisitor getBuildUpdateInfoVisitor(StringBuilder whereSql,Map<String,Object> params);

	@Override
	public Date forDate() {
		return this.forField(Date.class);
	}

	@Override
	public Boolean forBoolean() {
		return this.forField(Boolean.class);
	}

	@Override
	public List<Long> listLong() {
		return this.listField(Long.class);
	}

	@Override
	public List<Double> listDouble() {
		return this.listField(Double.class);
	}

	@Override
	public List<Date> listDate() {
		return this.listField(Date.class);
	}

	@Override
	public List<Integer> listInt() {
		return this.listField(Integer.class);
	}

	protected  class UpdateCriteriaInfo{
		String[] updateField;
		String namedWhereSql;
		Map<String,Object> params;
		
		public UpdateCriteriaInfo(String[] updateField,String namedWhereSql,Map<String,Object> params) {
			this.updateField=updateField;
			this.namedWhereSql=namedWhereSql;
			this.params=params;
		}
	}


	@Override
	public NoLambdaCriteria<E> nlike(String fieldName, String value) {
		checkAddAnd();
		criterions.add(Restrictions.nlike(fieldName, value));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> nlike(String fieldName, String value, MatchMode mathMode) {
		checkAddAnd();
		criterions.add(Restrictions.nlike(fieldName, value, mathMode));
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> field(String fieldName) {
		queryFields.add(fieldName);
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> offset(long offset) {
		this.offset=offset;
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> limit(int limit) {
		this.limit=limit;
		return  this;
	}

	@Override
	public NoLambdaCriteria<E> set(String fieldName, Object value) {
		this.setters.add(new SetCriterion(fieldName,value));
		return  this;
	}

}
